/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
	"fmt"
	"log"
	"os"
	"os/exec"

	"gitee.com/chuanjinge/go-container/container/info"
	_ "gitee.com/chuanjinge/go-container/container/space"
	"github.com/spf13/cobra"
)

// execCmd represents the exec command
var execCmd = &cobra.Command{
	Use:   "exec",
	Short: "exec <CONTAINER_ID>",
	Long:  `进入一个已经启动的容器进程的命名空间之中查看相关信息`,
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return fmt.Errorf("请指定容器ID")
		}
		return nil
	},
	Run: func(cmd *cobra.Command, args []string) {
		if os.Getenv("_TINYDOCKER_EXEC_MODE") == "child" {
			log.Printf("ERROR MODE")
			return
		}
		// 就算是指定了 LockOSThread ，也无法让 setns(mnt_fd) 成功执行！！！
		// 只能将 setns 的逻辑放到 Cgo 中，并且是在 go runtime 启动之前执行了！！！
		// -- 锁定当前Goroutine到当前操作系统线程
		// -- 因为该程序会调用 setns ，而setns会将当前线程加入到其他的namespace，如果当前线程被调度到其他的系统线程，就导致我们切换的namespace用不上了
		//runtime.LockOSThread()
		//defer runtime.UnlockOSThread()
		fmt.Println("exec called")
		containerId := args[0]
		cInfo, err := info.GetContainerInfoByContainerId(containerId)
		if err != nil {
			fmt.Println("[GetContainerInfoByContainerId]", err.Error())
			return
		}

		// 构造proc信息路径
		namespaceDir := fmt.Sprintf("/proc/%d/ns", cInfo.State.Pid)
		if _, err := os.Stat(namespaceDir); err != nil {
			if os.IsNotExist(err) {
				fmt.Printf("容器进程运行信息%s不存在\n", namespaceDir)
			} else {
				fmt.Printf("容器进程运行信息%s获取异常\n", namespaceDir)
			}
			return
		}
		// 如下代码废弃，确实不起作用!!!
		// --注意调整命名空间的顺序，确保 mnt 最后设置，因为挂载命名空间会对文件系统视图产生影响--
		// --否则的话，当进入挂载命名空间之后，如果再执行 open 其他的nspath的话，就会提示找不到，因为文件系统已经发生变化了。--
		/*
			namespaceList := []string{"ipc", "uts", "pid", "net", "mnt"}
			for _, namespace := range namespaceList {
				log.Printf("namespace: %s\n", namespace)
				namespacePath := filepath.Join(namespaceDir, namespace)
				fd, err := unix.Open(namespacePath, unix.O_RDONLY, 0)
				if err != nil {
					log.Fatalf("Failed to open namespace: %v", err)
				}
				fmt.Printf("fd-new:%d\n", fd)
				defer unix.Close(fd)

				if err := unix.Setns(fd, 0); err != nil {
					log.Fatalf("Failed to set namespace:%s %v", namespacePath, err)
				}
				fmt.Printf("Namespace %s switched!; fd:%d\n", namespace, fd)
			}
		*/

		exePath, err := os.Executable()
		if err != nil {
			log.Fatal("Executable Error: ", err.Error())
		}
		childCmd := exec.Command(exePath, "exec", containerId) // 重新执行 tinydocker.out exec // exec后边的参数其实无所谓，因为在子进程中通过环境变量接收运行参数
		childCmd.Stdin = os.Stdin
		childCmd.Stdout = os.Stdout
		childCmd.Stderr = os.Stderr
		// 唤起相同程序的子进程，子进程的执行命令依赖环境变量
		// 如下
		//   _TINYDOCKER_EXEC_PID  目标欲查看的子进程的ID
		//   _TINYDOCKER_EXEC_MODE 这个环境变量标明进程运行环境是子进程模式
		//   _TINYDOCKER_EXEC_CMD_xxx 这个环境变量设置为 docker exec CONTAINERID -- bash -c "echo hello" 中 -- 后的参数，即一个符合 POSIX命令行规则的参数列表，此处即 bash -c "echo hello"
		//     其中 _TINYDOCKER_EXEC_CMD_COUNT 表示命令的个数
		//         _TINYDOCKER_EXEC_CMD_0 表示第一个命令行参数
		//         _TINYDOCKER_EXEC_CMD_1 表示第二个命令行参数
		//         .... 依次类推
		//
		childCmdExecMode := "_TINYDOCKER_EXEC_MODE=child"
		childCmdExecPid := fmt.Sprintf("_TINYDOCKER_EXEC_PID=%d", cInfo.State.Pid)
		allChildCmdEnv := append(os.Environ(), []string{childCmdExecMode, childCmdExecPid}...)
		execCommand := args[1:]
		childCmdExecCmdCount := fmt.Sprintf("_TINYDOCKER_EXEC_CMD_COUNT=%d", len(execCommand))
		allChildCmdEnv = append(allChildCmdEnv, childCmdExecCmdCount)
		// 具体参数
		for k, v := range execCommand {
			childCmdnExecCmdParam := fmt.Sprintf("_TINYDOCKER_EXEC_CMD_%d=%s", k, v)
			allChildCmdEnv = append(allChildCmdEnv, childCmdnExecCmdParam)
		}
		//log.Println("子进程环境变量: ", allChildCmdEnv)
		childCmd.Env = allChildCmdEnv
		// 同步等待子进程结束，而不是 start 那种异步的方式
		// 同步的过程中因为子进程在等待标准输入的输入，所以如果子进程要运行的程序是等待标准输入的输入的话，主进程也会在tty界面进行标准输入的等待!!!
		if err = childCmd.Run(); err != nil {
			log.Fatalf("Exec container %s error %v", containerId, err)
		}
	},
}

func init() {
	rootCmd.AddCommand(execCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// execCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// execCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
